<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <script src="https://cdn.jsdelivr.net/npm/vue"></script>
  <style>
    .list-move {
      transition: all .3s ease-in-out;
    }

    #app {
      position: relative;
      width: 920px;
      height: 600px;
      margin: 20px auto;
      background-color: #fff;
    }

    .net {
      font-size: 0;
      width: 600px;
      height: 600px;
      transform-origin: top left;
      overflow: hidden;
    }

    .grid {
      display: inline-block;
      background-color: #aaa;
      background-repeat: no-repeat;
      background-size: 600px 600px;
      overflow: hidden;
    }

    .block {
      opacity: 0;
    }

    .right {
      position: absolute;
      top: 0;
      left: 620px;
      width: 300px;
    }

    .btn-box {
      padding: 10px 0;
      text-align: center;
      line-height: 30px;
    }

    .btn {
      background-color: #fff;
      border: 1px solid black;
      outline: none;
      cursor: pointer;
    }

    .btn:hover {
      background-color: #ddd;
    }

    .btn.active {
      background-color: #ddd;
    }

    .full {
      width: 300px;
      height: 300px;
      background-size: 300px 300px;
      background-repeat: no-repeat;
    }

    .url-box {
      display: block;
      padding: 10px 0;
    }

    .url-box p {
      text-align: center;
    }

    .url-box textarea {
      width: 100%;
      resize: none;
    }
  </style>
</head>

<body>
  <div id="app">
    <transition-group name="list" class="net" tag="div">
      <div class="grid" v-for="item of list" :key="item" :class="{block:item===level*level}"
        :style="{'background-position':getPosition(item-1),'height':wh,'width':wh,'background-image':url}"></div>
    </transition-group>
    <div class="right">
      <div class="btn-box">
        <button @click="shuffle" class="btn">打乱</button>
        <span>选择难度：</span>
        <button class="btn" :class="{active:level===3}" @click="selectLevel(3)">简单</button>
        <button class="btn" :class="{active:level===4}" @click="selectLevel(4)">普通</button>
        <button class="btn" :class="{active:level===5}" @click="selectLevel(5)">困难</button>
      </div>
      <div class="full" :style="{'background-image':url}"></div>
      <label class="url-box">
        <p>复制图片地址：</p>
        <textarea type="text" rows="3" v-model="nowUrl"></textarea>
      </label>
      <p v-show="!isTrue">图片地址无效，已切换回默认图片</p>
    </div>
  </div>
  <script>
    let vm = new Vue({
      el: "#app",
      data: {
        net: [],
        pos: [],
        list: [],
        level: 3,
        step: 200,
        defaultImg: "http://file02.16sucai.com/d/file/2015/0128/8b0f093a8edea9f7e7458406f19098af.jpg",
        img: '',
        nowUrl: '',
        canMove: true,
        isTrue: true,
        lastPosition: { x: 0, y: 0 }
      },
      computed: {
        wh() {
          return this.step + 'px';
        },
        url() {
          return `url('${this.img}')`;
        }
      },
      watch: {
        nowUrl(url) {
          this.judge(url);
        }
      },
      methods: {
        judge: (function (fn) {
          let task = null;
          return function (url) {
            let context = this;
            if (task !== null)
              clearTimeout(task);
            task = setTimeout(() => {
              if (!url) {
                context.img = context.defaultImg;
                context.isTrue = true;
                return
              }
              let ImgObj = new Image();
              ImgObj.src = url;
              ImgObj.onload = () => {
                context.img = url;
                context.isTrue = true;
              };
              ImgObj.onerror = () => {
                context.img = context.defaultImg;
                context.isTrue = false;
              };
              task = null;
            }, 200);
          }
        })(),
        selectLevel(n) {
          this.level = n;
          this.pos = [n - 1, n - 1];
          this.step = 600 / n;
          this.net = [];
          let nn = n * n, i, x, y;
          for (i = 0; i < nn; i++) {
            x = Math.floor(i / n);
            y = i % n;
            this.net[x] = this.net[x] || [];
            this.net[x][y] = i + 1;
          }
          this.shuffle();
        },
        getPosition(a) {
          let { level, step } = this;
          return `${-(a % level * step)}px ${-(Math.floor(a / level) * step)}px`;
        },
        isWin() {
          let flag = 0, res = this.level * this.level;
          this.list.forEach((item, index) => flag += item === index + 1 ? 1 : 0);
          if (flag === res) {
            this.canMove = false;
            setTimeout(() => {
              if (confirm('You win!\nTry again?')) {
                this.selectLevel(this.level);
                this.canMove = true;
              }
            }, 300);
          }
        },
        shuffle() {
          this.canMove = true;
          let a;
          for (let i = 0; i < 100; i++) {
            a = Math.floor(Math.random() * 4);
            switch (a) {
              case 0:
                this.moveX(false, true);
                break;
              case 1:
                this.moveX(true, true);
                break;
              case 2:
                this.moveY(false, true);
                break;
              case 3:
                this.moveY(true, true);
                break;
            }
          }
          this.list = this.net.flat();
        },
        moveX(flag, flush) {
          if (!this.canMove)
            return;
          let x = this.pos[0], y = this.pos[1], to = flag ? y - 1 : y + 1;
          if ((y === 0 && flag) || (!flag && y === this.level - 1))
            return;
          let temp = this.net[x][y];
          this.net[x][y] = this.net[x][to];
          this.net[x][to] = temp;
          this.pos[1] = to;
          if (!flush) {
            this.list = this.net.flat();
            this.isWin();
          }
        },
        moveY(flag, flush) {
          if (!this.canMove)
            return;
          let x = this.pos[0], y = this.pos[1], to = flag ? x - 1 : x + 1;
          if ((x === 0 && flag) || (!flag && x === this.level - 1))
            return;
          let temp = this.net[x][y];
          this.net[x][y] = this.net[to][y];
          this.net[to][y] = temp;
          this.pos[0] = to;
          if (!flush) {
            this.list = this.net.flat();
            this.isWin();
          }
        }
      },
      created() {
        this.img = this.defaultImg;
        this.selectLevel(3);
      },
      mounted() {
        window.addEventListener('keydown', e => {
          if (e.key === 'ArrowLeft') {
            this.moveX(false);
          } else if (e.key === 'ArrowUp') {
            this.moveY(false);
          } else if (e.key === 'ArrowRight') {
            this.moveX(true);
          } else if (e.key === 'ArrowDown') {
            this.moveY(true);
          }
        });
        window.addEventListener('mousedown', e => {
          this.lastPosition = { x: e.clientX, y: e.clientY };
        });
        window.addEventListener('mouseup', e => {
          let nx = e.clientX, ny = e.clientY;
          let { x, y } = this.lastPosition;
          Math.abs(nx - x) > Math.abs(ny - y) ? this.moveX(nx > x) : this.moveY(ny > y);
        });
      },
    })
  </script>
</body>

</html>